联盟推广者查询接口 API 文档
📋 接口概述
接口功能:获取当前策展人(Curator)的所有通过联盟邀请链接注册的推广者列表
业务场景:策展人查看自己邀请的联盟推广者信息,支持按关键字搜索(姓名、邮箱、手机号、店铺名等)
核心特性:
- 仅返回通过
INVITATION_LINK类型邀请的推广者关联 - 支持可选的关键字模糊搜索
- 自动过滤已删除的关联记录(
deletedAt IS NULL) - 包含策展人的子域名信息用于构建推广链接
🔗 接口地址
GET /promoter-association/affiliate-promoters
环境:
- 生产环境:
https://katana-api.1m.app - 测试环境:
https://staging.katana-api.1m.app
📝 请求头(Headers)
| Header 名称 | 是否必填 | 类型 | 说明 | 示例值 |
|---|---|---|---|---|
Authorization |
✅ 是 | string | JWT 认证令牌 | Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... |
from |
✅ 是 | string | 客户端标识 | client |
timezone |
否 | string | 时区信息 | Asia/Shanghai |
x-track-id |
否 | string | 请求追踪ID | 316d56b9-7ccf-4f84-a79d-1c90fc31fc89 |
[!note] 认证说明 该接口需要 JWT Token 认证,用户角色必须是
BUSINESS_PARTNER或具有策展人权限。
📥 请求参数(Query Parameters)
| 参数名 | 是否必填 | 类型 | 说明 | 示例值 |
|---|---|---|---|---|
keyword |
❌ 否 | string | 搜索关键字,支持模糊匹配 | 123 |
关键字搜索范围:
- 用户姓名(
first_name+last_name) - 邮箱地址(
email) - 手机号码(
phone_number) - 个性化链接(
vanity_url) - 店铺名称(
store_name)
[!tip] 搜索行为
- 不提供
keyword时,返回所有关联的推广者信息- 提供
keyword时,仅在关联的推广者中进行模糊搜索- 搜索使用 PostgreSQL 的
ILIKE进行大小写不敏感匹配
📤 响应结构
响应类型
HTTP 状态码: 200 OK
响应格式: application/json
响应内容: QueryAffiliateLinkResponse[] 数组
响应字段说明
| 字段名 | 类型 | 是否必返 | 说明 | |
|---|---|---|---|---|
id |
string | ✅ 是 | 推广者用户 ID(UUID) | |
curatorId |
string | ✅ 是 | 策展人用户 ID(当前用户 ID) | |
promoterFullName |
string | ✅ 是 | 推广者全名(优先使用姓名,否则邮箱/手机号) | |
vanityUrl |
string \ | null | ✅ 是 | 推广者个性化链接 |
logo |
string \ | null | ✅ 是 | 推广者头像 URL |
affiliateCode |
string | ✅ 是 | 推广者联盟代码 | |
subDomainUrl |
string | ✅ 是 | 策展人子域名 URL(无子域名时为空字符串) | |
invitationType |
string | ✅ 是 | 邀请类型(固定为 INVITATION_LINK) |
TypeScript 类型定义
interface QueryAffiliateLinkResponse {
id: string; // 推广者用户 ID
curatorId: string; // 策展人用户 ID
promoterFullName: string; // 推广者全名
vanityUrl: string | null; // 个性化链接
logo: string | null; // 头像 URL
affiliateCode: string; // 联盟代码
subDomainUrl: string; // 策展人子域名
invitationType: string; // 邀请类型
}
interface QueryAffiliateLinkRequest {
keyword?: string; // 可选搜索关键字
}
💡 成功响应示例
请求示例
curl -X GET 'https://staging.katana-api.1m.app/promoter-association/affiliate-promoters?keyword=123' \
-H 'accept: application/json, text/plain, */*' \
-H 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' \
-H 'from: client' \
-H 'timezone: Asia/Shanghai'
响应示例(200 OK)
[
{
"id": "e1bf696f-5040-4801-a04c-52e7801eea9f",
"curatorId": "550e8400-e29b-41d4-a716-446655440000",
"promoterFullName": "张三",
"vanityUrl": "zhangsan-shop",
"logo": "https://cdn.example.com/avatars/zhangsan.jpg",
"affiliateCode": "AFF123456",
"subDomainUrl": "https://zhangsan.pear.us",
"invitationType": "INVITATION_LINK"
},
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"curatorId": "550e8400-e29b-41d4-a716-446655440000",
"promoterFullName": "lisi@example.com",
"vanityUrl": null,
"logo": null,
"affiliateCode": "AFF789012",
"subDomainUrl": "https://zhangsan.pear.us",
"invitationType": "INVITATION_LINK"
}
]
空结果示例
[]
⚠️ 错误响应示例
401 Unauthorized - 未认证
{
"statusCode": 401,
"message": "Unauthorized",
"error": "Unauthorized"
}
原因: JWT Token 无效、过期或未提供
403 Forbidden - 无权限
{
"statusCode": 403,
"message": "Forbidden resource",
"error": "Forbidden"
}
原因: 用户角色不是 BUSINESS_PARTNER 或无策展人权限
404 Not Found - 用户不存在
{
"statusCode": 404,
"message": "Resource not found",
"error": "Not Found",
"curatorId": "550e8400-e29b-41d4-a716-446655440000"
}
原因: 当前用户在数据库中不存在(已被删除)
🔄 业务流程
泳道图
sequenceDiagram
autonumber
participant Client as 客户端
participant Controller as PromoterAssociationController
participant Service as PromoterAssociationService
participant Repo as PromoterAssociationRepository
participant UserRepo as UserMetaRepository
participant DB as PostgreSQL Database
Client->>Controller: GET /affiliate-promoters?keyword=xxx
activate Controller
Controller->>Controller: 从 JWT 获取 curatorId
Controller->>UserRepo: findUserById(curatorId)
UserRepo-->>Controller: User | null
alt 用户不存在
Controller-->>Client: 404 Not Found
end
par 并行查询
Controller->>Service: findPromoterAssociationsByLinkType()
Service->>Repo: findMany(where)
Repo->>DB: SELECT * FROM PromoterAssociation<br/>WHERE curatorId=? AND invitationType='INVITATION_LINK'<br/>AND deletedAt IS NULL
DB-->>Repo: PromoterAssociation[]
Repo-->>Service: PromoterAssociation[]
Service-->>Controller: PromoterAssociation[]
and
Controller->>Repo: getSubdomainByUserId(curatorId)
Repo->>DB: SELECT * FROM SubDomain<br/>WHERE userId=?
DB-->>Repo: SubDomain[]
Repo-->>Controller: SubDomain[]
end
Controller->>Controller: 提取 promoterIds(去重)
alt 存在 keyword
Controller->>Service: searchAffiliateLinkUsersInfo(userIds, keyword)
Service->>DB: $queryRaw(ILIKE 多字段搜索)
DB-->>Service: QueryAffiliateLinkUserInfo[]
else 无 keyword
Controller->>Service: findAffiliateLinkUsersInfo(userIds)
Service->>DB: SELECT * FROM User WHERE id IN (?)
DB-->>Service: User[]
Service->>Service: 转换为 UserEntity 并映射
Service-->>Controller: QueryAffiliateLinkUserInfo[]
end
Controller->>Controller: 构建 promoterUserMap<br/>过滤并组装响应数据
Controller-->>Client: 200 OK + QueryAffiliateLinkResponse[]
deactivate Controller
数据流图
graph TD
A[客户端请求] --> B[解析 JWT 获取 curatorId]
B --> C{验证策展人存在?}
C -->|否| D[返回 404]
C -->|是| E[并行查询]
E --> F[查询 PromoterAssociation]
E --> G[查询 SubDomain]
F --> H[提取 promoterIds 去重]
G --> I[获取子域名 URL]
H --> J{有 keyword?}
J -->|是| K[执行 ILIKE 模糊搜索]
J -->|否| L[批量查询用户信息]
K --> M[构建用户信息映射]
L --> M
M --> N[组装响应数据]
I --> N
N --> O[返回响应数组]
🗄️ 数据库操作
涉及数据表
1. PromoterAssociation 表
查询条件:
SELECT *
FROM "PromoterAssociation"
WHERE "curatorId" = $1
AND "invitationType" = 'INVITATION_LINK'
AND "deletedAt" IS NULL
ORDER BY "createdAt" DESC;
索引建议:
- 复合索引:
(curatorId, invitationType, deletedAt)
2. User 表
无关键字时:
SELECT *
FROM "User"
WHERE "id" IN ($1, $2, ...);
有关键字时(使用原始 SQL):
SELECT u.id, u.first_name, u.last_name, u.email,
u.phone_number, u.vanity_url, u.affiliate_code,
u.logo, u.store_name
FROM "User" u
WHERE u.id IN ($1, $2, ...)
AND (
(u.first_name || ' ' || u.last_name) ILIKE $keyword
OR u.email ILIKE $keyword
OR u.phone_number ILIKE $keyword
OR u.vanity_url ILIKE $keyword
OR u.store_name ILIKE $keyword
);
3. SubDomain 表
SELECT *
FROM "SubDomain"
WHERE "userId" = $1
ORDER BY "createdAt" DESC;
数据表关系
erDiagram
PromoterAssociation ||--o{ User : "promoterId"
PromoterAssociation }o--|| User : "curatorId"
User ||--o{ SubDomain : "userId"
PromoterAssociation {
uuid id PK
uuid curatorId FK
uuid promoterId FK
string invitationType
timestamp deletedAt
timestamp createdAt
}
User {
uuid id PK
string first_name
string last_name
string email
string phone_number
string vanity_url
string affiliate_code
string logo
string store_name
}
SubDomain {
uuid id PK
uuid userId FK
string subDomainUrl
timestamp createdAt
}
⚙️ 业务逻辑说明
1. 用户验证阶段
- 从 JWT Token 中提取
curatorId(当前用户 ID) - 调用
UserMetaRepository.findUserById()验证用户存在 - 用户不存在时抛出
ResourceNotFound异常
2. 数据查询阶段
并行查询(使用 Promise.all):
- PromoterAssociation: 查询所有
INVITATION_LINK类型的关联记录- 筛选条件:
curatorId,invitationType = INVITATION_LINK,deletedAt = null - 排序: 按
createdAt DESC
- 筛选条件:
- SubDomain: 查询策展人的子域名记录
- 取最新的子域名(按
createdAt DESC)
- 取最新的子域名(按
3. 用户信息获取阶段
根据有无关键字选择查询方式:
| 场景 | 查询方式 | 特点 |
|---|---|---|
| 无关键字 | findUsersByIds() |
批量查询 User 表,性能较高 |
| 有关键字 | searchAffiliateLinkUsersInfo() |
使用 $queryRaw 执行 ILIKE 多字段搜索 |
4. 数据组装阶段
- 去重: 使用
Array.from(new Set(promoterIds))去除重复的推广者 ID - 构建映射: 使用
keyBy()将用户信息转换为id为键的 Map - 过滤: 遍历关联记录,跳过用户信息不存在的记录
- 组装: 构建
QueryAffiliateLinkResponse对象
5. 全名处理逻辑
fullName =
userEntity.fullName !== '' ? userEntity.fullName :
userEntity.email !== '' ? userEntity.email :
userEntity.phoneNumber
优先级: 全名 > 邮箱 > 手机号
📊 注意事项
1. 权限控制
- 该接口仅限
BUSINESS_PARTNER角色访问 - 策展人只能查看自己邀请的推广者,无法查看其他策展人的数据
2. 邀请类型
- 接口硬编码仅返回
INVITATION_LINK类型的关联 - 不会返回
AFFILIATE类型或其他类型的推广者关联
3. 软删除处理
- 已删除的关联记录(
deletedAt IS NOT NULL)会被自动过滤 - 符合平台数据保留策略
4. 空值处理
| 字段 | 空值处理 |
|---|---|
subDomainUrl |
无子域名时返回空字符串(非 null) |
vanityUrl |
无个性化链接时返回 null |
logo |
无头像时返回 null |
5. 性能优化建议
- 并行查询: 策展人信息、关联记录、子域名使用
Promise.all并行获取 - 批量查询: 用户信息使用
IN子句批量查询 - 去重: 推广者 ID 使用
Set去重,减少重复查询
6. 搜索限制
- 搜索使用
ILIKE进行模糊匹配 - 关键字搜索仅在关联的推广者范围内进行,不是全局搜索
- 前端建议添加防抖(debounce)处理,避免频繁请求
7. 数据一致性
- 如果推广者在关联创建后被删除,该记录会被静默过滤(不抛出异常)
- 返回结果可能少于
PromoterAssociation表中的记录数
🔗 相关接口
- [[kat-KAT-10550-curator-affiliate-link-create-20260226|创建联盟推广链接]]
- [[kat-short-link-internal-create-20260227|短链接生成接口]]
📝 更新日志
| 版本 | 日期 | 作者 | 变更说明 |
|---|---|---|---|
| 1.0.0 | 2026-02-27 | Claude | 初始版本 |